#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <mutex>
#include "log.hpp"
#include "util.hpp"
namespace ns_index{
    
    struct DocInfo{
        std::string title;
        std::string content;
        std::string url;
        uint64_t doc_id;
    };

    struct InvertedElem{
        uint64_t doc_id;
        std::string word;
        int weight;
        InvertedElem():weight(0){}
    };

    typedef std::vector<InvertedElem> InvertedList;

    class Index{
        private:
            std::vector<DocInfo> forward_index;
            std::unordered_map<std::string, InvertedList> inverted_index;
            static Index *instance;
            static std::mutex mtx;

        private:
            Index(){}
            Index(const Index&) = delete;
        public:
            static Index* GetInstance()
            {
                if(instance == nullptr)
                {
                    mtx.lock();
                    if(instance == nullptr){
                        instance = new Index();
                    }
                    mtx.unlock();
                }
                return instance;
            }
            
            DocInfo *GetForwardIndex(size_t index)
            {
                if(index > forward_index.size()){
                    LOG(WARNING, "index out of size!");
                    return nullptr;
                }
                return &forward_index[index];
            }

            InvertedList* GetInvertedList(const std::string& word)
            {
                auto iter = inverted_index.find(word);
                if(iter == inverted_index.end()){
                    std::cerr << word << " have no InvertedList" << std::endl;
                    return nullptr;
                }
                return &(iter->second);
            }

            bool BuildIndex(const std::string& input)
            {
                std::ifstream in(input, std::ios::in | std::ios::binary);
                if(!in.is_open()){
                    LOG(FATAL, input + " open file false!");
                    return false;
                }
                std::string line;

                int count = 0;
                while(getline(in, line))
                {
                    DocInfo * doc = BuildForwardIndex(line);
                    if(nullptr == doc){
                        LOG(FATAL, "BuildForwardIndex false!");
                        return false;
                    }

                    if(!BuildInvertedList(*doc)){
                        LOG(FATAL, "BuildInvertedList false!");
                        return false;
                    }
                    if((++count) % 50 == 0){
                        LOG(NORMAL, "当前的已经建立的索引文档: " + std::to_string(count));
                    }
                }
                return true;
            }
        private:
            DocInfo* BuildForwardIndex(const std::string& line)
            {
                std::vector<std::string> results;
                std::string sep = "\3";
                ns_util::StringUtil::Split(line, &results, sep);

                //std::cout << results.size() << std::endl;

                if(results.size() != 3){
                    LOG(FATAL, "Split false!");
                    return nullptr;
                }
                DocInfo doc;
                doc.title = results[0];
                doc.content = results[1];
                doc.url = results[2];
                doc.doc_id = forward_index.size();
                forward_index.push_back(std::move(doc));
                return &forward_index.back();
            }

            bool BuildInvertedList(const DocInfo &doc)
            {
                struct word_cnt{
                    int title_count = 0;
                    int content_count = 0;
                };

                std::unordered_map<std::string, word_cnt> word_map;
                std::vector<std::string> title_words, content_words;

                ns_util::JiebaUtil::CutString(doc.title, &title_words);
                for(auto& s : title_words)
                {
                    boost::to_lower(s);
                    word_map[s].title_count++;
                }    
                ns_util::JiebaUtil::CutString(doc.content, &content_words);
                for(auto& s : content_words)
                {
                    boost::to_lower(s);
                    word_map[s].content_count++;
                }    
#define X 10
#define Y 1
                for(auto &item : word_map)
                {
                    InvertedElem elm;
                    elm.doc_id = doc.doc_id;
                    elm.word = item.first;
                    elm.weight = item.second.title_count * X + item.second.content_count * Y;
                    InvertedList& inverted_list = inverted_index[item.first];
                    inverted_list.push_back(std::move(elm));
                }
                return true;
            }
    };
    Index* Index::instance = nullptr;
    std::mutex Index::mtx;
};